Go 您所在的位置:网站首页 go for range原理 Go

Go

2024-07-06 19:44| 来源: 网络整理| 查看: 265

Go-for range aside section ._1OhGeD · · 1278 次点击 · · 开始浏览     这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。 第一次,站长亲自招 Gopher 了>>>

Go中 有关循环,有两种,一种是for i :=0;i < len(x); i++ 的经典模式,另外一种是for k, v := range xxx。在用第二种方式时,有一些坑,这儿简单总结一下,希望对跟我一样有疑惑的朋友 有所帮助~

1 for range 支持的数据类型

for range 目前支持slice、map、string以及channel。 在每一种的使用过程中编译器都会对其做转换。接着往下看

2 for range 应用与原理 2.1 slice 与 array

为了方便讲解,下文的 默认操作例子 for k, v := range a {}

slice 跟 array 的for range 操作,会被编译器转换成经典for循环模式。 编译之后,大体是这样的:

ha := a hv1 := 0 hn := len(ha) v1 := hv1 for ; hv1 < hn; hv1++ { // ... }

在转换之前,首先是进行了slice 或者array进行了拷贝,这样在实际遍历过程中,对v 的操作就不是对原slice/array操作了。如果需要影响原slice的内容,则需要操作&a[k] 了。

举个例子直观看一下:

func main() { a := []int{1,2,3} for _, v := range a { // 这儿的v 是 a的副本的value值。所以这样不会改变a的值 v++ } fmt.Println(a) // [1 2 3] // 如果想改变原来数组中的内容,其实可以直接操作原slice 地址 for k, v := range a { a[k] = v + 1 } fmt.Println(a) // [2 3 4] }

再来个例子:

func main() { a := []int{1,2,3} for _, v := range a { // v 是a副本的值,所以只有3个哦~ a = append(a, v) } fmt.Println(a) // 1 2 3 1 2 3 } 2.2 map 2.2.1 map for range 的无序性

map 的 for range 操作,编译之后会被展开为 mapiterinit跟 mapiternext。 编译之后,大体是这样的:

ha := a hit := hiter(n.Type) th := hit.Type mapiterinit(typename(t), ha, &hit) for ; hit.key != nil; mapiternext(&hit) { key := *hit.key val := *hit.val }

mapiterinit 的主要用途是:

随机找一个桶作为遍历的开始(这也是为什么每次for range同一个map结果不一样的原因)

mapiternext 的主要用途是:

遍历桶中的数据 当前桶中数据遍历完毕,寻找下一个桶 所有桶都遍历完毕,则结束。

注意:每次for range map ,都会产生不同的顺序。

2.2.2 map for range 过程中添加、删除元素

我们再看一个 写了个例子:

func main() { amap := map[string]int{"zhangpeng":3, "chris": 1, "lmm" : 2} for k, v := range amap { fmt.Println(k) //time.Sleep(time.Microsecond * 50) k = k + "-1" amap[k] = v } fmt.Println(amap) }

发现每次执行的结论是不一样的。 这是为什么呢?

如果 map 中的元素在还没有被遍历到时就被移除了,后续的迭代中这个元素就不会再出现 如果 map 中的元素是在迭代过程中被添加的,那么在后续的迭代这个元素可能出现也可能被跳过。 2.3 string

字符串的遍历与数组和哈希表非常相似,只是在遍历的过程中会获取字符串中索引对应的字节,然后将字节转换成 rune,我们在遍历字符串时拿到的值都是 rune 类型的变量,其实类似 for i, r := range s {} 的结构都会被转换成如下的形式:

ha := s for hv1 := 0; hv1 < len(ha); { hv1t := hv1 hv2 := rune(ha[hv1]) if hv2 < utf8.RuneSelf { hv1++ } else { hv2, hv1 = decoderune(h1, hv1) } v1, v2 = hv1t, hv2 }

如果当前的 rune 是 ASCII 的,那么只会占用一个字节长度,这时只需要将索引加一,但是如果当前的 rune 占用了多个字节就会使用 decoderune 进行解码。

举个例子:

func main() { astr := "hello 中国" for k, v := range astr { fmt.Println(k, v, string(v)) } }

结果是:

0 104 h 1 101 e 2 108 l 3 108 l 4 111 o 5 32 6 20013 中 9 22269 国

如果使用经典的for 循环,遍历string, 就会出现乱码问题了。

func main() { astr := "hello 中国" for i := 0; i < len(astr); i++ { fmt.Println(i, astr[i], string(astr[i])) } }

结论是这样的:

0 104 h 1 101 e 2 108 l 3 108 l 4 111 o 5 32 6 228 ä 7 184 ¸ 8 173 ­ 9 229 å 10 155 11 189 ½ 2.4 channel

for v := range ch {} 编译之后大体是这样的:

ha := a hv1, hb :=


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有